﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace FluentMef
{
    public static class ContractNameServices
{
    // Fields
    private const char ArrayClosingBracket = ']';
    private const char ArrayOpeningBracket = '[';
    private const char ArraySeparator = ',';
    private const char ContractNameGenericArgumentSeparator = ',';
    private const char ContractNameGenericClosingBracket = ')';
    private const char ContractNameGenericOpeningBracket = '(';
    private const char CustomModifiersSeparator = ' ';
    private const char GenericArityBackQuote = '`';
    private const char NamespaceSeparator = '.';
    private const char NestedClassSeparator = '+';
    private const char PointerSymbol = '*';
    private const char ReferenceSymbol = '&';
    [ThreadStatic]
    private static Dictionary<Type, string> typeIdentityCache;

    // Methods
    private static Type FindArrayElementType(Type type)
    {
        Type type2 = type;
        while (((type2 = type2.GetElementType()) != null) && type2.IsArray)
        {
        }
        return type2;
    }

    private static string FindGenericTypeName(string genericName)
    {
        int index = genericName.IndexOf('`');
        if (index > -1)
        {
            genericName = genericName.Substring(0, index);
        }
        return genericName;
    }

    private static int GetGenericArity(Type type)
    {
        if (type.DeclaringType == null)
        {
            return type.GetGenericArguments().Length;
        }
        int length = type.DeclaringType.GetGenericArguments().Length;
        int num2 = type.GetGenericArguments().Length;
        Assumes.IsTrue(num2 >= length);
        return (num2 - length);
    }

    public static string GetTypeIdentity(Type type)
    {
        Assumes.NotNull<Type>(type);
        string typeIdentityFromMethod = null;
        if (!TypeIdentityCache.TryGetValue(type, out typeIdentityFromMethod))
        {
            if (!type.IsAbstract && type.IsSubclassOf(typeof(Delegate)))
            {
                typeIdentityFromMethod = GetTypeIdentityFromMethod(type.GetMethod("Invoke"));
            }
            else
            {
                StringBuilder typeName = new StringBuilder();
                WriteTypeWithNamespace(typeName, type);
                typeIdentityFromMethod = typeName.ToString();
            }
            TypeIdentityCache.Add(type, typeIdentityFromMethod);
        }
        return typeIdentityFromMethod;
    }

    internal static string GetTypeIdentityFromMethod(MethodInfo method)
    {
        StringBuilder typeName = new StringBuilder();
        WriteTypeWithNamespace(typeName, method.ReturnType);
        typeName.Append("(");
        ParameterInfo[] parameters = method.GetParameters();
        for (int i = 0; i < parameters.Length; i++)
        {
            if (i != 0)
            {
                typeName.Append(",");
            }
            WriteTypeWithNamespace(typeName, parameters[i].ParameterType);
        }
        typeName.Append(")");
        return typeName.ToString();
    }

    private static void WriteArrayType(StringBuilder typeName, Type type)
    {
        Type type2 = FindArrayElementType(type);
        WriteType(typeName, type2);
        Type type3 = type;
        do
        {
            WriteArrayTypeDimensions(typeName, type3);
        }
        while (((type3 = type3.GetElementType()) != null) && type3.IsArray);
    }

    private static void WriteArrayTypeDimensions(StringBuilder typeName, Type type)
    {
        typeName.Append('[');
        int arrayRank = type.GetArrayRank();
        for (int i = 1; i < arrayRank; i++)
        {
            typeName.Append(',');
        }
        typeName.Append(']');
    }

    private static void WriteByRefType(StringBuilder typeName, Type type)
    {
        WriteType(typeName, type.GetElementType());
        typeName.Append('&');
    }

    internal static void WriteCustomModifiers(StringBuilder typeName, string customKeyword, Type[] types)
    {
        typeName.Append(' ');
        typeName.Append(customKeyword);
        Queue<Type> genericTypeArguments = new Queue<Type>(types);
        WriteTypeArgumentsString(typeName, types.Length, false, genericTypeArguments);
        Assumes.IsTrue(genericTypeArguments.Count == 0, "Expecting genericTypeArguments queue to be empty.");
    }

    private static void WriteGenericType(StringBuilder typeName, Type type, bool isDefinition, Queue<Type> genericTypeArguments)
    {
        if (type.DeclaringType != null)
        {
            if (type.DeclaringType.IsGenericType)
            {
                WriteGenericType(typeName, type.DeclaringType, isDefinition, genericTypeArguments);
            }
            else
            {
                WriteNonGenericType(typeName, type.DeclaringType);
            }
            typeName.Append('+');
        }
        WriteGenericTypeName(typeName, type, isDefinition, genericTypeArguments);
    }

    private static void WriteGenericTypeName(StringBuilder typeName, Type type, bool isDefinition, Queue<Type> genericTypeArguments)
    {
        Assumes.IsTrue(type.IsGenericType, "Expecting type to be a generic type");
        int genericArity = GetGenericArity(type);
        string str = FindGenericTypeName(type.GetGenericTypeDefinition().Name);
        typeName.Append(str);
        WriteTypeArgumentsString(typeName, genericArity, isDefinition, genericTypeArguments);
    }

    private static void WriteNonGenericType(StringBuilder typeName, Type type)
    {
        if (type.DeclaringType != null)
        {
            WriteType(typeName, type.DeclaringType);
            typeName.Append('+');
        }
        if (type.IsArray)
        {
            WriteArrayType(typeName, type);
        }
        else if (type.IsPointer)
        {
            WritePointerType(typeName, type);
        }
        else if (type.IsByRef)
        {
            WriteByRefType(typeName, type);
        }
        else
        {
            typeName.Append(type.Name);
        }
    }

    private static void WritePointerType(StringBuilder typeName, Type type)
    {
        WriteType(typeName, type.GetElementType());
        typeName.Append('*');
    }

    private static void WriteType(StringBuilder typeName, Type type)
    {
        if (type.IsGenericType)
        {
            Queue<Type> genericTypeArguments = new Queue<Type>(type.GetGenericArguments());
            WriteGenericType(typeName, type, type.IsGenericTypeDefinition, genericTypeArguments);
            Assumes.IsTrue(genericTypeArguments.Count == 0, "Expecting genericTypeArguments queue to be empty.");
        }
        else
        {
            WriteNonGenericType(typeName, type);
        }
    }

    private static void WriteTypeArgumentsString(StringBuilder typeName, int argumentsCount, bool isDefinition, Queue<Type> genericTypeArguments)
    {
        if (argumentsCount != 0)
        {
            typeName.Append('(');
            for (int i = 0; i < argumentsCount; i++)
            {
                Assumes.IsTrue(genericTypeArguments.Count > 0, "Expecting genericTypeArguments to contain at least one Type");
                Type type = genericTypeArguments.Dequeue();
                if (!isDefinition)
                {
                    WriteTypeWithNamespace(typeName, type);
                }
                typeName.Append(',');
            }
            typeName.Remove(typeName.Length - 1, 1);
            typeName.Append(')');
        }
    }

    private static void WriteTypeWithNamespace(StringBuilder typeName, Type type)
    {
        if (!string.IsNullOrEmpty(type.Namespace))
        {
            typeName.Append(type.Namespace);
            typeName.Append('.');
        }
        WriteType(typeName, type);
    }

    // Properties
    private static Dictionary<Type, string> TypeIdentityCache
    {
        get
        {
            return (typeIdentityCache = typeIdentityCache ?? new Dictionary<Type, string>());
        }
    }
}

}
